package net.stuffrepos.bricknetcenter.util;

import java.util.HashMap;
import java.util.Map;
import net.stuffrepos.util.cache.CacheableMapValue;

/**
 *
 * @author Eduardo H. Bogoni <eduardobogoni@gmail.com>
 */
public abstract class BuilderFactory<SourceType, ResultType, KeyType> {

    private final boolean cacheResults;

    public BuilderFactory(boolean cacheResults) {
        this.cacheResults = cacheResults;
    }

    public static interface Builder<SourceType, ResultType> {

        public ResultType build(SourceType source);
    }
    private final Map<KeyType, Builder<SourceType, ResultType>> builders = new HashMap<KeyType, Builder<SourceType, ResultType>>();
    private final CacheableMapValue<SourceType, ResultType> results = new CacheableMapValue<SourceType, ResultType>() {

        @Override
        protected ResultType calculate(SourceType key) {
            return buildResult(key);
        }
    };

    private ResultType buildResult(SourceType source) {
        Builder<SourceType, ResultType> builder = getBuilder(getSourceKey(source));
        if (builder != null) {
            return builder.build(source);
        } else {
            throw new RuntimeException(
                    String.format("Builder not found to \"%s\" on \"%s\"", source, BuilderFactory.this));
        }
    }

    public void addBuilder(KeyType clazz, Builder<SourceType, ResultType> builder) {
        builders.put(clazz, builder);
    }

    public ResultType getResult(SourceType source) {
        if (cacheResults) {
            return results.getValue(source);
        } else {
            return buildResult(source);
        }
    }

    private Builder<SourceType, ResultType> getBuilder(KeyType keyType) {
        if (keyType == null) {
            return null;
        } else {
            Builder<SourceType, ResultType> builder = builders.get(keyType);

            if (builder == null) {
                return getBuilder(getParentKey(keyType));
            }

            return builder;
        }
    }

    protected abstract KeyType getSourceKey(SourceType source);

    protected abstract KeyType getParentKey(KeyType source);

    public abstract static class ClassKeyed<SourceType, ResultType> extends BuilderFactory<SourceType, ResultType, Class> {

        public ClassKeyed(boolean cacheResults) {
            super(cacheResults);
        }

        @Override
        protected Class getParentKey(Class source) {
            return source.getSuperclass();
        }
    }
}
